package com.gl.portal.system.service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import javax.persistence.criteria.Predicate;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.data.domain.Sort;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import com.gl.common.constant.Constants;
import com.gl.common.enums.MenuTypeEnum;
import com.gl.common.enums.StatusEnum;
import com.gl.common.exception.CustomException;
import com.gl.common.util.SecurityUtils;
import com.gl.common.util.StringUtils;
import com.gl.framework.security.LoginUser;
import com.gl.framework.web.domain.TreeSelect;
import com.gl.portal.system.entity.SysMenu;
import com.gl.portal.system.repository.SysMenuRepository;
import com.gl.portal.system.repository.SysRoleMenuRepository;
import com.gl.portal.system.repository.SysMenuRepository.SelectedMenuIdVo;
import com.gl.portal.system.repository.SysMenuRepository.UserMenuPermsVo;
import com.gl.portal.system.vo.MetaVo;
import com.gl.portal.system.vo.RouterVo;
import com.gl.portal.system.vo.SysMenuVo;
import com.gl.portal.system.vo.SysUserVo;

/**
 * 菜单服务实现类
 */
@Service
public class SysMenuService {
    public static final String PREMISSION_STRING = "perms[\"{0}\"]";

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private SysMenuRepository sysMenuRepository;

    @Autowired
    private SysRoleMenuRepository sysRoleMenuRepository;

    /**
     * 根据用户查询系统菜单列表
     *
     * @param userId 用户ID
     * @return 菜单列表
     */
    public List<SysMenuVo> selectMenuList(Long userId) {
        return selectMenuList(null, userId);
    }

    /**
     * 查询系统菜单列表
     *
     * @param vo, userId 菜单信息
     * @return 菜单列表
     */
    public List<SysMenuVo> selectMenuList(SysMenuVo vo, Long userId) {
        List<SysMenuVo> menuList = null;
        // 管理员显示所有菜单信息
        if (SecurityUtils.isAdmin(userId)) {
            menuList = this.findMenuList(vo);
        } else {
            menuList = this.findMenuListByUserId(vo, userId);
        }
        return menuList;
    }

    /**
     * 查询系统菜单列表
     *
     * @param vo 菜单VO
     * @return 菜单列表
     */
    private List<SysMenuVo> findMenuList(SysMenuVo vo) {
        return sysMenuRepository.findAll((root, query, criteriaBuilder) -> {
            Predicate predicate = criteriaBuilder.and();

            if (vo != null) {
                if (StringUtils.isNotBlank(vo.getMenuName())) {
                    predicate = criteriaBuilder.and(predicate, criteriaBuilder.like(root.get("menuName"), "%" + vo.getMenuName() + "%"));
                }
                if (vo.getVisible() != null) {
                    predicate = criteriaBuilder.and(predicate, criteriaBuilder.equal(root.get("visible"), vo.getVisible()));
                }
                if (vo.getStatus() != null) {
                    predicate = criteriaBuilder.and(predicate, criteriaBuilder.equal(root.get("status"), vo.getStatus()));
                }
            }

            return predicate;
        }, Sort.by("sortNum")).stream().map(this::convert).collect(Collectors.toList());
    }

    /**
     * 数据转换
     *
     * @param entity 菜单实体
     * @return 菜单VO
     */
    private SysMenuVo convert(SysMenu entity) {
        BeanCopier beanCopier = BeanCopier.create(SysMenu.class, SysMenuVo.class, false);
        SysMenuVo vo = new SysMenuVo();
        beanCopier.copy(entity, vo, null);
        return vo;
    }

    /**
     * 查询用户系统菜单列表
     *
     * @param vo     菜单VO
     * @param userId 用户ID
     * @return 菜单列表
     */
    private List<SysMenuVo> findMenuListByUserId(SysMenuVo vo, Long userId) {
        final String SQL = "SELECT DISTINCT m.id, m.parent_id, m.menu_name, m.path, m.component, m.visible, m.`status`, IFNULL(m.perms,'') AS perms, m.menu_type, m.icon, m.sort_num, m.create_time \n" +
                "FROM sys_menu m \n" +
                "LEFT JOIN sys_role_menu rm ON m.id = rm.menu_id \n" +
                "LEFT JOIN sys_user_role ur ON rm.role_id = ur.role_id \n" +
                "LEFT JOIN sys_role r ON ur.role_id = r.id \n";
        final String ORDERBY_SQL = "ORDER BY m.parent_id, m.sort_num";

        StringBuilder where = new StringBuilder("WHERE ur.user_id = ? \n");
        List<Object> args = new ArrayList<>();
        args.add(userId);

        if (vo != null) {
            if (StringUtils.isNotEmpty(vo.getMenuName())) {
                where.append("AND m.menu_name LIKE CONCAT('%', ?, '%') \n");
                args.add(vo.getMenuName());
            }
            if (vo.getVisible() != null) {
                where.append("AND m.visible = ? \n");
                args.add(vo.getVisible());
            }
            if (vo.getStatus() != null) {
                where.append("AND m.`status` = ? \n");
                args.add(vo.getStatus());
            }
        }

        return jdbcTemplate.query(SQL + where + ORDERBY_SQL, new BeanPropertyRowMapper<>(SysMenuVo.class), args.toArray());
    }

    /**
     * 根据用户ID查询权限
     *
     * @param userId 用户ID
     * @return 权限列表
     */
    public Set<String> selectMenuPermsByUserId(Long userId) {
        List<UserMenuPermsVo> perms = sysMenuRepository.findMenuPermsByUserId(userId);
        Set<String> permsSet = new HashSet<>();
        for (UserMenuPermsVo menu : perms) {
            if (menu == null) {
                continue;
            }

            String perm = menu.getPerms();
            if (StringUtils.isNotEmpty(perm)) {
                permsSet.addAll(Arrays.asList(perm.trim().split(",")));
            }
        }
        return permsSet;
    }

    /**
     * 根据用户ID查询菜单
     *
     * @param userId 用户名称
     * @return 菜单列表
     */
    public List<SysMenuVo> selectMenuTreeByUserId(Long userId) {
        List<SysMenu> menuList = null;
        if (SecurityUtils.isAdmin(userId)) {
            menuList = sysMenuRepository.findByMenuTypeInAndStatus(Arrays.asList(MenuTypeEnum.DIR.value(), MenuTypeEnum.MENU.value()), StatusEnum.NORMAL.value(), Sort.by("parentId", "sortNum"));
        } else {
            menuList = sysMenuRepository.selectMenuTreeByUserId(userId);
        }

        List<SysMenuVo> menus = new ArrayList<>();
        if (menuList != null) {
            menus = menuList.stream().map(this::convert).collect(Collectors.toList());
        }

        return this.getChildPerms(menus, 0);
    }

    /**
     * 根据角色ID查询菜单树信息
     *
     * @param roleId 角色ID
     * @return 选中菜单列表
     */
    public List<Long> selectMenuListByRoleId(Long roleId) {
        return sysMenuRepository.selectMenuListByRoleId(roleId).stream().map(SelectedMenuIdVo::getId).collect(Collectors.toList());
    }

    /**
     * 构建前端路由所需要的菜单
     *
     * @param menus 菜单列表
     * @return 路由列表
     */
    public List<RouterVo> buildMenus(List<SysMenuVo> menus) {
        List<RouterVo> routers = new LinkedList<>();
        for (SysMenuVo menu : menus) {
            RouterVo router = new RouterVo();
            router.setHidden(0 == menu.getVisible());
            router.setName(getRouteName(menu));
            router.setPath(getRouterPath(menu));
            router.setComponent(getComponent(menu));
            router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon()));
            List<SysMenuVo> cMenus = menu.getChildren();
            if (!cMenus.isEmpty() && menu.getMenuType() != null && MenuTypeEnum.DIR.value() == menu.getMenuType()) {
                router.setAlwaysShow(true);
                router.setRedirect("noRedirect");
                router.setChildren(buildMenus(cMenus));
            } else if (isMeunFrame(menu)) {
                List<RouterVo> childrenList = new ArrayList<RouterVo>();
                RouterVo children = new RouterVo();
                children.setPath(menu.getPath());
                children.setComponent(menu.getComponent());
                children.setName(StringUtils.capitalize(menu.getPath()));
                children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon()));
                childrenList.add(children);
                router.setChildren(childrenList);
            }
            routers.add(router);
        }
        return routers;
    }

    /**
     * 构建前端所需要树结构
     *
     * @param menus 菜单列表
     * @return 树结构列表
     */
    public List<SysMenuVo> buildMenuTree(List<SysMenuVo> menus) {
        List<SysMenuVo> returnList = new ArrayList<>();
        for (Iterator<SysMenuVo> iterator = menus.iterator(); iterator.hasNext(); ) {
            SysMenuVo t = (SysMenuVo) iterator.next();
            // 根据传入的某个父节点ID,遍历该父节点的所有子节点
            if (t.getParentId() == 0) {
                recursionFn(menus, t);
                returnList.add(t);
            }
        }
        if (returnList.isEmpty()) {
            returnList = menus;
        }
        return returnList;
    }

    /**
     * 构建前端所需要下拉树结构
     *
     * @param menus 菜单列表
     * @return 下拉树结构列表
     */
    public List<TreeSelect> buildMenuTreeSelect(List<SysMenuVo> menus) {
        List<SysMenuVo> menuTrees = buildMenuTree(menus);
        return menuTrees.stream().map(TreeSelect::new).collect(Collectors.toList());
    }

    /**
     * 根据菜单ID查询信息
     *
     * @param menuId 菜单ID
     * @return 菜单信息
     */
    public SysMenuVo selectMenuById(Long menuId) {
        Optional<SysMenu> optional = sysMenuRepository.findById(menuId);
        if (!optional.isPresent()) {
            return null;
        }

        return convert(optional.get());
    }

    /**
     * 是否存在菜单子节点
     *
     * @param menuId 菜单ID
     * @return 结果
     */
    public boolean hasChildByMenuId(Long menuId) {
        int result = sysMenuRepository.countByParentId(menuId);
        return result > 0 ? true : false;
    }

    /**
     * 查询菜单使用数量
     *
     * @param menuId 菜单ID
     * @return 结果
     */
    public boolean checkMenuExistRole(Long menuId) {
        int result = sysRoleMenuRepository.countByMenuId(menuId);
        return result > 0 ? true : false;
    }

    /**
     * 新增菜单信息
     *
     * @param menu 菜单信息
     */
    public void saveMenu(SysMenuVo menu) {
        BeanCopier beanCopier = BeanCopier.create(SysMenuVo.class, SysMenu.class, false);
        SysMenu entity = new SysMenu();
        beanCopier.copy(menu, entity, null);

        LoginUser loginUser = SecurityUtils.getLoginUser();
        SysUserVo user = loginUser.getUser();

        entity.setCreateUserId(user.getId());
        entity.setCreateTime(new Date());
        sysMenuRepository.save(entity);
    }

    /**
     * 修改菜单信息
     *
     * @param menu 菜单信息
     */
    public void updateMenu(SysMenuVo menu) {
        Optional<SysMenu> optional = sysMenuRepository.findById(menu.getId());
        if (!optional.isPresent()) {
            throw new CustomException("菜单数据不存在");
        }
        SysMenu entity = optional.get();

        LoginUser loginUser = SecurityUtils.getLoginUser();
        SysUserVo user = loginUser.getUser();

        BeanCopier beanCopier = BeanCopier.create(SysMenuVo.class, SysMenu.class, false);
        beanCopier.copy(menu, entity, null);
        entity.setUpdateUserId(user.getId());
        entity.setUpdateTime(new Date());

        sysMenuRepository.save(entity);
    }

    /**
     * 删除菜单管理信息
     *
     * @param menuId 菜单ID
     */
    public void deleteMenuById(Long menuId) {
        sysMenuRepository.deleteById(menuId);
        sysRoleMenuRepository.deleteByMenuId(menuId);
    }

    /**
     * 校验菜单名称是否唯一
     *
     * @param menu 菜单信息
     * @return 结果
     */
    public String checkMenuNameUnique(SysMenuVo menu) {
        Long menuId = StringUtils.isNull(menu.getId()) ? -1L : menu.getId();
        SysMenu info = sysMenuRepository.findByMenuNameAndParentId(menu.getMenuName(), menu.getParentId());
        if (StringUtils.isNotNull(info) && info.getId().longValue() != menuId.longValue()) {
            return Constants.NOT_UNIQUE;
        }
        return Constants.UNIQUE;
    }

    /**
     * 获取路由名称
     *
     * @param menu 菜单信息
     * @return 路由名称
     */
    public String getRouteName(SysMenuVo menu) {
        String routerName = StringUtils.capitalize(menu.getPath());
        // 一级目录（类型为目录）
        if (isMeunFrame(menu)) {
            routerName = StringUtils.EMPTY;
        }
        return routerName;
    }

    /**
     * 获取路由地址
     *
     * @param menu 菜单信息
     * @return 路由地址
     */
    public String getRouterPath(SysMenuVo menu) {
        String routerPath = menu.getPath();
        // 一级目录（类型为目录）
        if (0 == menu.getParentId().intValue() && menu.getMenuType() != null && MenuTypeEnum.DIR.value() == menu.getMenuType()) {
            routerPath = "/" + menu.getPath();
        }
        // 一级目录（类型为菜单）
        else if (isMeunFrame(menu)) {
            routerPath = "/";
        }
        return routerPath;
    }

    /**
     * 获取组件信息
     *
     * @param menu 菜单信息
     * @return 组件信息
     */
    public String getComponent(SysMenuVo menu) {
        String component = Constants.LAYOUT;
        if (StringUtils.isNotEmpty(menu.getComponent()) && !isMeunFrame(menu)) {
            component = menu.getComponent();
        }
        return component;
    }

    /**
     * 是否为菜单内部跳转
     *
     * @param menu 菜单信息
     * @return 结果
     */
    public boolean isMeunFrame(SysMenuVo menu) {
        return menu.getParentId().intValue() == 0 && menu.getMenuType() != null && MenuTypeEnum.MENU.value() == menu.getMenuType();
    }

    /**
     * 根据父节点的ID获取所有子节点
     *
     * @param list     分类表
     * @param parentId 传入的父节点ID
     * @return
     */
    public List<SysMenuVo> getChildPerms(List<SysMenuVo> list, int parentId) {
        List<SysMenuVo> returnList = new ArrayList<>();
        for (Iterator<SysMenuVo> iterator = list.iterator(); iterator.hasNext(); ) {
            SysMenuVo t = (SysMenuVo) iterator.next();
            // 一、根据传入的某个父节点ID,遍历该父节点的所有子节点
            if (t.getParentId() == parentId) {
                recursionFn(list, t);
                returnList.add(t);
            }
        }
        return returnList;
    }

    /**
     * 递归列表
     *
     * @param list
     * @param t
     */
    private void recursionFn(List<SysMenuVo> list, SysMenuVo t) {
        // 得到子节点列表
        List<SysMenuVo> childList = getChildList(list, t);
        t.setChildren(childList);
        for (SysMenuVo tChild : childList) {
            if (hasChild(list, tChild)) {
                // 判断是否有子节点
                Iterator<SysMenuVo> it = childList.iterator();
                while (it.hasNext()) {
                    SysMenuVo n = (SysMenuVo) it.next();
                    recursionFn(list, n);
                }
            }
        }
    }

    /**
     * 得到子节点列表
     *
     * @param list
     * @param t
     * @return
     */
    private List<SysMenuVo> getChildList(List<SysMenuVo> list, SysMenuVo t) {
        List<SysMenuVo> tlist = new ArrayList<>();
        Iterator<SysMenuVo> it = list.iterator();
        while (it.hasNext()) {
            SysMenuVo n = (SysMenuVo) it.next();
            if (n.getParentId().longValue() == t.getId().longValue()) {
                tlist.add(n);
            }
        }
        return tlist;
    }

    /**
     * 判断是否有子节点
     *
     * @param list
     * @param t
     * @return
     */
    private boolean hasChild(List<SysMenuVo> list, SysMenuVo t) {
        return getChildList(list, t).size() > 0 ? true : false;
    }
}
